“Everything is related to everything else, but near things are more related than distant things.” -Waldo R. Tobler
Spatial autocorrelation is a way to measure how much things that are close to each other are similar or different. Imagine a usual rainy day in London. If it rains in one part of a town, it is also likely raining in nearby areas. This shows high spatial autocorrelation as weather in one place is similar to the weather in nearby places.
It is important to investigate spatial autocorrelation in each type of crime, particularly when we want to estimate risk of crime with place-based information. By analysing spatial autocorrelation, we can identify if there is any spatial pattern and hotspot for each type of crime.
Examples of Spatial Autocorrelation
(a) positive - clustering (b) negative - dispersion (c) random
The diagram1 above illustrate examples of spatial autocorrelation. Both (a) and (b) show some patterns, whereas with (c) we can’t explain how black and white boxes are located. Hence, (c) is completely random and shows no pattern, therefore, no autocorrelation.
For (a), we can also see black boxes are clustered at a corner. This is clustering, or positive autocorrleation. One of the common measures of spatial autocorrelation, Moran’s Index (or Moran’s I) is positive in the case of (a). With (b), we can see a checkerboard pattern. We can safely say (b) is dispersed, which has negative spatial autocorrelation, scoring negative on Moran’s I.
#--Subset crime data asb <-subset(crime_bnt, category =="anti social behaviour")vc <-subset(crime_bnt, category =="violent crime")other <-subset(crime_bnt, category =="other theft")vhc <-subset(crime_bnt, category =="vehicle crime")tfp <-subset(crime_bnt, category =="theft from the person")brg <-subset(crime_bnt, category =="burglary")
Global Spatial Autocorrelation
We will investigate if there is a spatial autocorrelation overall in each type of crime. First of all, we need to define neighbours as spatial autocorrelation is contingent on how we define neighbours. While there are many ways to do so, we will deinfe points as neighbours if they are within a certain distance. This distance is called critical threshold.
#--Create weight from nbdist_weight_asb <-nb2listw(nb_dist_band_asb)
The critical threshold for ASB was 660m. That is, ASB points were considered neighbours #### Repeat ##### Define Function: create_dist_weight()
Code
create_dist_weight <-function(crime_type){#--Get X and Y coordinates coords <-st_coordinates(crime_type)#--To find a critical threshold, find the k-nearest neighbors for k = 1 knn1 <-knearneigh(coords)#--Convert k1 to nb k1 <-knn2nb(knn1)#--Calculate critical threshold: maximum distance between neighbours critical_threshold <-max(unlist(nbdists(k1, coords)))print(paste0("Critical threshold is: ", critical_threshold, "m"))#--Calculate distance-band weights nb_dist_band <-dnearneigh(coords, 0, critical_threshold)summary(nb_dist_band)#--Get cardinality: number of neighbours for each point dist_band_card <- spdep::card(nb_dist_band)#--Create weight from nb dist_weight <-nb2listw(nb_dist_band)print("Done")return(dist_weight)}
Count the Number of Neighbouring Points of Each Crime Point within 100m
Example with ASB
Code
#--Count counts_asb <-numeric(nrow(asb))for (i inseq_along(asb)){#--Get a ponit point <- asb[i,]$geometry#--Get buffer basically area within 100m buffer <-st_buffer(point, units::as_units(100, "m"))#--Test if the asb points intersect with buffer test <-st_intersects(asb$geometry, buffer)#--Calculate the number of points intersecting buffer n <-which(sapply(test, function(x) sum(x) !=0)) |>length()#--Exclude the point itself counts_asb[i] <- n -1}#--Assign the counts to the 'count' column of the spatial points objectasb$count <- counts_asb#--Check the structure of 'asb' to ensure the count variable is added correctlysummary(asb)#> category id month location_type #> Length:25551 Min. : 91710306 Length:25551 Length:25551 #> Class :character 1st Qu.: 97081088 Class :character Class :character #> Mode :character Median :103280983 Mode :character Mode :character #> Mean :103443093 #> 3rd Qu.:109502752 #> Max. :116586465 #> location_subtype location.street.id location.street.name#> Length:25551 Min. : 923463 Length:25551 #> Class :character 1st Qu.: 980725 Class :character #> Mode :character Median :1661421 Mode :character #> Mean :1336059 #> 3rd Qu.:1667149 #> Max. :1677900 #> geometry count #> POINT :25551 Min. :0.00e+00 #> epsg:27700 : 0 1st Qu.:0.00e+00 #> +proj=tmer...: 0 Median :0.00e+00 #> Mean :9.43e-03 #> 3rd Qu.:0.00e+00 #> Max. :1.03e+02
Repeat
Define Function: count_crime()
Code
count_crime <-function(crime, radius){#--Count counts <-numeric(nrow(crime))for (i inseq_along(crime)){#--Get a point point <- crime[i,]$geometry#--Get buffer basically area within the specified radius buffer <-st_buffer(point, units::as_units(radius, "m"))#--Test if the crime points intersect with buffer test <-st_intersects(crime$geometry, buffer)#--Calculate the number of points intersecting buffer n <-which(sapply(test, function(x) sum(x) !=0)) |>length()#--Exclude the point itself counts[i] <- n -1 }#--Assign the counts to the 'count' column of the spatial points object crime$count <- countsprint("Done")return(crime)}
Loop
Code
#--Count crime within 100m radiusl_crime <-map(l_crime, ~count_crime(.x, 100))#> [1] "Done"#> [1] "Done"#> [1] "Done"#> [1] "Done"#> [1] "Done"#> [1] "Done"
Perform Global Autocorrelation Test
Example with ASB
Code
# Conduct global Moran's I analysisset.seed(1234)moran_asb <- spdep::moran.test(asb$count, listw = dist_weight_asb)# using the count of ASB crimes within 100m of each point as the variable to assess for global spatial autocorrelation# --Print Moran's I test resultsprint(moran_asb) #> #> Moran I test under randomisation#> #> data: asb$count #> weights: dist_weight_asb #> #> Moran I statistic standard deviate = 2.6573, p-value = 0.003939#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 6.969130e-04 -3.913894e-05 7.672758e-08# weak positive autocorrelation, that is, nearby points tend to have similar values
Repeat
Code
l_moran <-vector("list", length(l_crime))l_moran <-map2(.x = l_crime, .y = l_weight, .f =~spdep::moran.test(.x$count, listw = .y))lapply(l_moran, print)#> #> Moran I test under randomisation#> #> data: .x$count #> weights: .y #> #> Moran I statistic standard deviate = 2.6573, p-value = 0.003939#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 6.969130e-04 -3.913894e-05 7.672758e-08 #> #> #> Moran I test under randomisation#> #> data: .x$count #> weights: .y #> #> Moran I statistic standard deviate = -0.32804, p-value = 0.6286#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -1.647028e-04 -4.194807e-05 1.400344e-07 #> #> #> Moran I test under randomisation#> #> data: .x$count #> weights: .y #> #> Moran I statistic standard deviate = -0.17873, p-value = 0.5709#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -2.546378e-04 -1.105705e-04 6.496998e-07 #> #> #> Moran I test under randomisation#> #> data: .x$count #> weights: .y #> #> Moran I statistic standard deviate = 0.50015, p-value = 0.3085#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 2.829501e-04 -7.399186e-05 5.093188e-07 #> #> #> Moran I test under randomisation#> #> data: .x$count #> weights: .y #> #> Moran I statistic standard deviate = 0.46265, p-value = 0.3218#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 6.284823e-04 -3.846154e-04 4.794996e-06 #> #> #> Moran I test under randomisation#> #> data: .x$count #> weights: .y #> #> Moran I statistic standard deviate = 1.1177, p-value = 0.1319#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 1.929608e-03 -1.477978e-04 3.454662e-06#> $asb#> #> Moran I test under randomisation#> #> data: .x$count #> weights: .y #> #> Moran I statistic standard deviate = 2.6573, p-value = 0.003939#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 6.969130e-04 -3.913894e-05 7.672758e-08 #> #> #> $vc#> #> Moran I test under randomisation#> #> data: .x$count #> weights: .y #> #> Moran I statistic standard deviate = -0.32804, p-value = 0.6286#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -1.647028e-04 -4.194807e-05 1.400344e-07 #> #> #> $other#> #> Moran I test under randomisation#> #> data: .x$count #> weights: .y #> #> Moran I statistic standard deviate = -0.17873, p-value = 0.5709#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -2.546378e-04 -1.105705e-04 6.496998e-07 #> #> #> $vhc#> #> Moran I test under randomisation#> #> data: .x$count #> weights: .y #> #> Moran I statistic standard deviate = 0.50015, p-value = 0.3085#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 2.829501e-04 -7.399186e-05 5.093188e-07 #> #> #> $tfp#> #> Moran I test under randomisation#> #> data: .x$count #> weights: .y #> #> Moran I statistic standard deviate = 0.46265, p-value = 0.3218#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 6.284823e-04 -3.846154e-04 4.794996e-06 #> #> #> $brg#> #> Moran I test under randomisation#> #> data: .x$count #> weights: .y #> #> Moran I statistic standard deviate = 1.1177, p-value = 0.1319#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 1.929608e-03 -1.477978e-04 3.454662e-06
Only ASB points within 100m showed a weak positive spatial auto-correlation. We will then run the test again with a different set of radius for the buffer.
Pull Everything Together
Code
run_global_sa <-function(crime, radius, weight){# Count crime within the specified radius crime <-count_crime(crime, radius)print("Crime count added!")# Run global autocorrelation testset.seed(1234) moran <- spdep::moran.test(crime$count, listw = weight)print(moran)return(moran)}
Loop run_global_sa() with different sets of radius
Code
c_radius <-c(50, 75, 200, 300)l_moran_radius <-vector("list", length(l_crime)-1)l_moran_radius <-map(l_moran_radius, ~vector("list", length(c_radius)))names(l_moran_radius) <-names(l_crime)[2:6]for (j in2:length(l_crime)){for (i inseq_along(c_radius)){ l_moran_radius[[(j-1)]][[i]] <-run_global_sa(crime = l_crime[[j]],radius = c_radius[[i]],weight = l_weight[[j]]) }}#> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = -0.36522, p-value = 0.6425#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -2.132557e-04 -4.194807e-05 2.200069e-07 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = -0.36437, p-value = 0.6422#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -1.774724e-04 -4.194807e-05 1.383394e-07 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = -0.36929, p-value = 0.644#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -2.114624e-04 -4.194807e-05 2.107093e-07 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = -0.40668, p-value = 0.6579#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -2.376532e-04 -4.194807e-05 2.315813e-07 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = -0.139, p-value = 0.5553#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -2.513731e-04 -1.105705e-04 1.026155e-06 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = -0.15471, p-value = 0.5615#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -2.484190e-04 -1.105705e-04 7.939177e-07 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = -0.2797, p-value = 0.6101#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -2.874777e-04 -1.105705e-04 4.000466e-07 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = -0.28403, p-value = 0.6118#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -3.152505e-04 -1.105705e-04 5.193188e-07 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = -0.10716, p-value = 0.5427#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -1.507892e-04 -7.399186e-05 5.136210e-07 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = -0.19079, p-value = 0.5757#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> -2.058521e-04 -7.399186e-05 4.776522e-07 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = 0.71142, p-value = 0.2384#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 4.972694e-04 -7.399186e-05 6.447957e-07 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = 0.39901, p-value = 0.3449#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 2.450347e-04 -7.399186e-05 6.392625e-07 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = 1.2437, p-value = 0.1068#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 2.546008e-03 -3.846154e-04 5.552078e-06 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = 1.1557, p-value = 0.1239#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 2.362469e-03 -3.846154e-04 5.649864e-06 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = 0.65148, p-value = 0.2574#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 1.217077e-03 -3.846154e-04 6.044352e-06 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = 0.59598, p-value = 0.2756#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 1.096501e-03 -3.846154e-04 6.176107e-06 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = 0.53712, p-value = 0.2956#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 8.495753e-04 -1.477978e-04 3.448102e-06 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = 0.59442, p-value = 0.2761#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 9.260347e-04 -1.477978e-04 3.263568e-06 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = 1.3761, p-value = 0.0844#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 2.521486e-03 -1.477978e-04 3.762654e-06 #> #> [1] "Done"#> [1] "Crime count added!"#> #> Moran I test under randomisation#> #> data: crime$count #> weights: weight #> #> Moran I statistic standard deviate = 1.3032, p-value = 0.09625#> alternative hypothesis: greater#> sample estimates:#> Moran I statistic Expectation Variance #> 2.479044e-03 -1.477978e-04 4.062848e-06
#--Plot the spatial data with color encoding for Local Moran's Iggplot() +geom_sf(data = asb, aes(colour = quadrant)) +geom_sf(data = bnt_shp, alpha =0, lwd =2) +theme_minimal() +labs(colour ="Quadrant") +ggtitle("Local Moran Cluster Map of ASB Points")